/*----------------------------------------------------------------------
* Copyright (C) 2013 - 2014: Sergey Smolyannikov <brainstream@yandex.com>
*
* This file is part of lxpamon.
*
* lxpamon is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lxpamon is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lxpamon. If not, see http://www.gnu.org/licenses/gpl-3.0.txt.
----------------------------------------------------------------------*/

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <pulse/pulseaudio.h>
#include <pulse/glib-mainloop.h>
#include "pamon.h"

#define ERSTR_UNKNOWN "Unknown error"
#define ERSTR_INVALID_SINK "Invalid sink"
#define ERSTR_CONNECTION_FAILED "Unable to connect to the pulseaudio daemon"

struct Pamon
{
    pa_context * pulse_context;
    pa_glib_mainloop * pulse_glib_mainloop;
    PamonVolumeChangedCallback volume_callback;
    PamonErrorCallback error_callback;
    int sink_id;
    void * userdata;
};

static void pulseContextStateCallback(pa_context * context, Pamon * pamon);
static void pulseContextCallback(pa_context * context, pa_subscription_event_type_t type, uint32_t idx, Pamon * pamon);
static void pulseSinkInfoCallback(pa_context * context, const pa_sink_info * info, int eol, Pamon * pamon);
static void performSinkHandler(pa_context * context, Pamon * pamon);
static void performError(Pamon * pamon, PamonError error);

Pamon * pamonInit(PamonInitData * init_data)
{
    Pamon * pamon = (Pamon *)malloc(sizeof(Pamon));
    memset(pamon, 0, sizeof(Pamon));
    if(init_data)
    {
        pamon->userdata = init_data->userdata;
        pamon->volume_callback = init_data->volume_callback;
        pamon->error_callback = init_data->error_callback;
        pamon->sink_id = init_data->sink_id;
    }
    pamon->pulse_glib_mainloop = pa_glib_mainloop_new(NULL);
    pa_mainloop_api * api = pa_glib_mainloop_get_api(pamon->pulse_glib_mainloop);
    pamon->pulse_context = pa_context_new(api, NULL);
    pa_context_set_state_callback(pamon->pulse_context, (pa_context_notify_cb_t)pulseContextStateCallback, pamon);
    pa_context_connect(pamon->pulse_context, NULL, 0, NULL);
    return pamon;
}

void pamonSetSinkId(Pamon * pamon, uint32_t id)
{
    if(!pamon || pamon->sink_id == id)
        return;
    pamon->sink_id = id;
    performSinkHandler(pamon->pulse_context, pamon);
}

void pamonClose(Pamon * pamon)
{
    if(!pamon) return;
    pa_context_disconnect(pamon->pulse_context);
    pa_context_unref(pamon->pulse_context);
    pa_glib_mainloop_free(pamon->pulse_glib_mainloop);
    free(pamon);
}

void pulseContextStateCallback(pa_context * context, Pamon * pamon)
{
    switch(pa_context_get_state(context))
    {
    case PA_CONTEXT_READY:
        pa_context_set_subscribe_callback(context, (pa_context_subscribe_cb_t)pulseContextCallback, pamon);
        pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
        performSinkHandler(context, pamon);
        break;
    case PA_CONTEXT_FAILED:
        performError(pamon, PAMON_ERROR_CONNECTION_FAILED);
        break;
    default:
        break;
    }
}

void performError(Pamon * pamon, PamonError error)
{
    if(pamon->error_callback)
        pamon->error_callback(error, pamon->userdata);
}

void performSinkHandler(pa_context * context, Pamon * pamon)
{   
    pa_operation * operation = pa_context_get_sink_info_by_index(context, pamon->sink_id,
        (pa_sink_info_cb_t)pulseSinkInfoCallback, pamon);
    pa_operation_unref(operation);
}

void pulseContextCallback(pa_context * context, pa_subscription_event_type_t type, uint32_t idx, Pamon * pamon)
{
    PAMON_UNUSED(type);
    PAMON_UNUSED(idx);
    performSinkHandler(context, pamon);
}

void pulseSinkInfoCallback(pa_context * context, const pa_sink_info * info, int eol, Pamon * pamon)
{
    if(eol == -1)
    {
        performError(pamon, PAMON_ERROR_INVALID_SINK);
    }
    if(info && pamon && pamon->volume_callback)
    {
        uint32_t volume = info->mute || !info->volume.channels ? 0 : info->volume.values[0];
        uint32_t percents = (uint32_t)round((double)(volume * 100) / PA_VOLUME_NORM);
        pamon->volume_callback(percents, pamon->userdata);
    }
}

const char * pamonTranslateError(PamonError error)
{
    switch(error)
    {
    case PAMON_ERROR_CONNECTION_FAILED:
        return ERSTR_CONNECTION_FAILED;
    case PAMON_ERROR_INVALID_SINK:
        return ERSTR_INVALID_SINK;
    default:
        return ERSTR_UNKNOWN;
    }
}
